package me.dwk.dandan.function.functions;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import lombok.extern.slf4j.Slf4j;
import me.dwk.dandan.function.AbstractFunction;
import me.dwk.dandan.function.CompoundVariable;
import me.dwk.dandan.function.ThreadVariables;
import me.dwk.dandan.function.exception.InvalidVariableException;

// @see org.apache.jmeter.functions.PackageTest for unit tests

/**
 * Function to split a string into variables
 * <p>
 * Parameters:
 * <ul>
 * <li>String to split</li>
 * <li>Variable name prefix</li>
 * <li>String to split on (optional, default is comma)</li>
 * </ul>
 * <p>
 * Returns: the input string
 * </p>
 * Also sets the variables:
 * <ul>
 * <li>VARNAME - the input string</li>
 * <li>VARNAME_n - number of fields found</li>
 * <li>VARNAME_1..n - fields</li>
 * </ul>
 *
 * @since 2.0.2
 */
@Slf4j
public class SplitFunction extends AbstractFunction {

	private static final List<String>	desc				= new LinkedList<>();

	private static final String			KEY					= "__split";			// $NON-NLS-1$

	// Number of parameters expected - used to reject invalid calls
	private static final int			MIN_PARAMETER_COUNT	= 2;

	private static final int			MAX_PARAMETER_COUNT	= 3;
	static {
		desc.add("String to split"); //$NON-NLS-1$
		desc.add("Name of variable in which to store the result (required)"); //$NON-NLS-1$
		desc.add("String to split on. Default is , (comma).");//$NON-NLS-1$
	}

	private Object[] values;

	public SplitFunction() {}

	/** {@inheritDoc} */
	@Override
	public String execute() throws InvalidVariableException {
		ThreadVariables vars = getVariables();

		String stringToSplit = ((CompoundVariable) values[0]).execute();
		String varNamePrefix = ((CompoundVariable) values[1]).execute().trim();
		String splitString = ",";

		if (values.length > 2) { // Split string provided
			splitString = ((CompoundVariable) values[2]).execute();
		}
		if (log.isDebugEnabled()) {
			log.debug("Split " + stringToSplit + " using " + splitString + " into " + varNamePrefix);
		}
		String[] parts = split(stringToSplit, splitString, "?");// $NON-NLS-1$

		vars.put(varNamePrefix, stringToSplit);
		vars.put(varNamePrefix + "_n", Integer.toString(parts.length));// $NON-NLS-1$
		for (int i = 1; i <= parts.length; i++) {
			if (log.isDebugEnabled()) {
				log.debug(parts[i - 1]);
			}
			vars.put(varNamePrefix + "_" + i, parts[i - 1]);// $NON-NLS-1$
		}
		vars.remove(varNamePrefix + "_" + (parts.length + 1));
		return stringToSplit;

	}

	/** {@inheritDoc} */
	@Override
	public void setParameters(Collection<CompoundVariable> parameters) throws InvalidVariableException {
		checkParameterCount(parameters, MIN_PARAMETER_COUNT, MAX_PARAMETER_COUNT);
		values = parameters.toArray();
	}

	/** {@inheritDoc} */
	@Override
	public String getReferenceKey() {
		return KEY;
	}

	/** {@inheritDoc} */
	@Override
	public List<String> getArgumentDesc() {
		return desc;
	}

	/**
	 * This is <em>almost</em> equivalent to the {@link String#split(String)} method
	 * in JDK 1.4. It is here to enable us to support earlier JDKs.
	 * <p>
	 * Note that unlike JDK1.4 split(), it optionally ignores leading split
	 * Characters, and the splitChar parameter is not a Regular expression
	 * <p>
	 * This piece of code used to be part of JMeterUtils, but was moved here because
	 * some JOrphan classes use it too.
	 *
	 * @param splittee
	 *            String to be split
	 * @param splitChar
	 *            Character(s) to split the string on, these are treated as a single
	 *            unit
	 * @param truncate
	 *            Should adjacent and leading/trailing splitChars be removed?
	 *
	 * @return Array of all the tokens; empty if the input string is {@code null} or
	 *         the splitChar is {@code null}
	 *
	 * @see #split(String, String, String)
	 *
	 */
	public static String[] split(	String splittee,
									String splitChar,
									boolean truncate) { // NOSONAR
		if (splittee == null || splitChar == null)
			return new String[0];
		final String EMPTY_ELEMENT = "";
		int spot;
		final int splitLength = splitChar.length();
		final String adjacentSplit = splitChar + splitChar;
		final int adjacentSplitLength = adjacentSplit.length();
		if (truncate) {
			while ((spot = splittee.indexOf(adjacentSplit)) != -1) {
				splittee = splittee.substring(0, spot + splitLength)
							+ splittee.substring(spot + adjacentSplitLength, splittee.length());
			}
			if (splittee.startsWith(splitChar)) {
				splittee = splittee.substring(splitLength);
			}
			if (splittee.endsWith(splitChar)) { // Remove trailing splitter
				splittee = splittee.substring(0, splittee.length() - splitLength);
			}
		}
		List<String> returns = new ArrayList<>();
		final int length = splittee.length(); // This is the new length
		int start = 0;
		spot = 0;
		while (start < length && (spot = splittee.indexOf(splitChar, start)) > -1) {
			if (spot > 0) {
				returns.add(splittee.substring(start, spot));
			} else {
				returns.add(EMPTY_ELEMENT);
			}
			start = spot + splitLength;
		}
		if (start < length) {
			returns.add(splittee.substring(start));
		} else if (spot == length - splitLength) {// Found splitChar at end of line
			returns.add(EMPTY_ELEMENT);
		}
		return returns.toArray(new String[returns.size()]);
	}

	public static String[] split(	String splittee,
									String splitChar) {
		return split(splittee, splitChar, true);
	}

	/**
	 * Takes a String and a tokenizer character string, and returns a new array of
	 * strings of the string split by the tokenizer character(s).
	 * <p>
	 * Trailing delimiters are significant (unless the default = null)
	 *
	 * @param splittee
	 *            String to be split.
	 * @param delims
	 *            Delimiter character(s) to split the string on
	 * @param def
	 *            Default value to place between two split chars that have nothing
	 *            between them. If null, then ignore omitted elements.
	 *
	 * @return Array of all the tokens.
	 *
	 * @throws NullPointerException
	 *             if splittee or delims are {@code null}
	 *
	 * @see #split(String, String, boolean)
	 * @see #split(String, String)
	 *
	 *      This is a rewritten version of JMeterUtils.split()
	 */
	public static String[] split(	String splittee,
									String delims,
									String def) {
		StringTokenizer tokens = new StringTokenizer(splittee, delims, def != null);
		boolean lastWasDelim = false;
		List<String> strList = new ArrayList<>();
		while (tokens.hasMoreTokens()) {
			String tok = tokens.nextToken();
			if (tok.length() == 1 // we have a single character; could be a token
				&& delims.contains(tok)) // it is a token
			{
				if (lastWasDelim) {// we saw a delimiter last time
					strList.add(def);// so add the default
				}
				lastWasDelim = true;
			} else {
				lastWasDelim = false;
				strList.add(tok);
			}
		}
		if (lastWasDelim) {
			strList.add(def);
		}
		return strList.toArray(new String[strList.size()]);
	}

}
